package com.github.zyf.ioaj.algorithm.swarmintelligence;

import com.github.zyf.ioaj.algorithm.Input;
import com.github.zyf.ioaj.algorithm.Output;
import com.github.zyf.ioaj.annotation.internal.Algorithm;
import com.github.zyf.ioaj.annotation.internal.BasicParam;
import com.github.zyf.ioaj.annotation.internal.TempParam;
import com.github.zyf.ioaj.constant.FileType;
import com.github.zyf.ioaj.constant.ParamType;
import com.github.zyf.ioaj.export.Report;
import com.github.zyf.ioaj.export.Tracker;
import com.github.zyf.ioaj.utils.basedata.ArrayUtils;

/**
 * <h1>Firefly Algorithm</h1>
 * <p>萤火虫算法</p>
 * <p>默认求解最小值，如果需要求解最大值，将目标函数乘以-1即可，最后得到的值再次乘以-1就是得到的最优值（自变量取值不用乘以-1）</p>
 * <p>需要计算则调用{@link #go()}</p>
 * <p>需要跟踪每次迭代则调用{@link #tracker(String, String, String, FileType)}</p>
 * <p>需要生成报告则调用{@link #report(String, String, String, FileType)}</p>
 * <p>
 * 萤火虫（firefly）种类繁多，主要分布在热带地区。
 * 大多数萤火虫在短时间内产生有节奏的闪光。
 * 这种闪光是由于生物发光的一种化学反应，萤火虫的闪光模式因种类而异。
 * 萤火虫算法(FA)是基于萤火虫的闪光行为，它是一种用于全局优化问题的智能随机算法，由Yang Xin-She（2009）提出。
 * 萤火虫通过下腹的一种化学反应-生物发光（bioluminescence）发光。
 * 这种生物发光是萤火虫求偶仪式的重要组成部分，也是雄性萤火虫和雌性萤火虫交流的主要媒介，
 * 发出光也可用来引诱配偶或猎物，同时这种闪光也有助于保护萤火虫的领地，并警告捕食者远离栖息地。
 * 在FA中，认为所有的萤火虫都是雌雄同体的，无论性别如何，它们都互相吸引。
 * 该算法的建立基于两个关键的概念：发出的光的强度和两个萤火虫之间产生的吸引力的程度。
 * </p>
 * <p>
 * 天然萤火虫在寻找猎物、吸引配偶和保护领地时表现出惊人的闪光行为，萤火虫大多生活在热带环境中。
 * 一般来说，它们产生冷光，如绿色、黄色或淡红色。
 * 萤火虫的吸引力取决于它的光照强度，对于任何一对萤火虫来说，较亮的萤火虫会吸引另一只萤火虫。
 * 所以，亮度较低的个体移向较亮的个体，同时光的亮度随着距离的增加而降低。
 * 萤火虫的闪光模式可能因物种而异，在一些萤火虫物种中，雌性会利用这种现象猎食其他物种；
 * 有些萤火虫在一大群萤火虫中表现出同步闪光的行为来吸引猎物，雌萤火虫从静止的位置观察雄萤火虫发出的闪光，
 * 在发现一个感兴趣趣的闪光后，雌性萤火虫会做出反应，发出闪光，求偶仪式就这样开始了。
 * 一些雌性萤火虫会产生其他种类萤火虫的闪光模式，来诱捕雄性萤火虫并吃掉它们。
 * </p>
 * <p>
 * 萤火虫算法模拟了萤火虫的自然现象。
 * 真实的萤火虫自然地呈现出一种离散的闪烁模式，而萤火虫算法假设它们总是在发光。
 * 为了模拟萤火虫的这种闪烁行为，Yang Xin-She提出了了三条规则（Yang，2009）：
 * </p>
 * <ol>
 *     <li>假设所有萤火虫都是雌雄同体的，因此一只萤火虫可能会被其他任何萤火虫吸引。</li>
 *     <li>萤火虫的亮度决定其吸引力的大小，较亮的萤火虫吸引较暗的萤火虫。如果没有萤火虫比被考虑的萤火虫更亮，它就会随机移动。</li>
 *     <li>函数的最优值与萤火虫的亮度成正比。</li>
 * </ol>
 * <p>Wang et al.(2017)提出了FA自适应参数以及自适应beta_0，这里再将函数的最优值与萤火虫的亮度的关系扩展为正相关</p>
 *
 * @author zyf
 */
@Algorithm
public class FA extends SwarmIntelligenceAlgorithm {

    /**
     * 萤火虫位置
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_2, desc = "萤火虫位置")
    private double[][] p;

    /**
     * 萤火虫的函数值
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "萤火虫的函数值")
    private double[] f;

    /**
     * 萤火虫自身的最大亮度
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "萤火虫自身的最大亮度")
    private double[] I_0;

    /**
     * 萤火虫之间的光强
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_2, desc = "萤火虫之间的光强")
    private double[][] I;

    /**
     * 光强吸收系数
     */
    @BasicParam(type = ParamType.DOUBLE, desc = "光强吸收系数")
    private double gamma;

    /**
     * 最大吸引度
     */
    @TempParam(type = ParamType.DOUBLE, desc = "最大吸引度")
    private double beta_0;

    /**
     * 吸引度
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "吸引度")
    private double[] beta;

    /**
     * 步长因子
     */
    @TempParam(type = ParamType.DOUBLE, desc = "步长因子")
    private double alpha;

    /**
     * 当前最优解对应的自变量取值
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "当前最优解对应的自变量取值")
    private double[] x;

    /**
     * 当前最优值
     */
    @TempParam(type = ParamType.DOUBLE, desc = "当前最优值")
    private double y;

    /**
     * 抽象算法构造函数，需要提供输入量，即提前初始化好{@link Input}对象
     *
     * @param input {@link Input}
     */
    public FA(Input input) {
        super(input);
        init();
    }

    private void init() {
        setIter(1000);
        int n = getN();
        int D = getInput().getVarNum();
        this.p = new double[n][D];
        this.f = new double[n];
        this.I = new double[n][n];
        this.I_0 = new double[n];
        this.x = new double[D];
        this.beta = new double[n];
        this.beta_0 = 1;
        this.gamma = 0.000001;
        this.alpha = 0.97;

        int i, j;
        for (i = 0; i < n; i++) {
            for (j = 0; j < D; j++) {
                p[i][j] = getInput().randomValueInRange(j);
            }
            f[i] = getInput().getFunction().apply(p[i]);
            I_0[i] = Math.exp(-f[i]);
        }
        calcAllI();
        this.x = p[0].clone();
        this.y = f[0];
    }

    /**
     * 每次迭代扫描一遍
     *
     * @param t 迭代次数
     */
    private void scan(int t) {
        int n = getN();
        int D = getInput().getVarNum();
        int i, k, j;
        calcAllI();
        alpha = alpha * (1.0 - t * 1.0 / getIter());
        beta_0 = Math.random() < 0.5 ? Math.random() : beta_0;
        int index;
        double r;
        for (i = 0; i < n; i++) {
            index = ArrayUtils.maxWithIndex(I[i]);
            r = calcDistance(p[i], p[index]);
            beta[i] = beta_0 * Math.exp(-gamma * r);
            for (j = 0; j < D; j++) {
                p[i][j] = p[i][j] + beta[i] * (p[index][j] - p[i][j]) + alpha * (2 * Math.random() - 1);
                // 防止越界
                if (p[i][j] < getInput().getLb()[j]) p[i][j] = getInput().getLb()[j];
                if (p[i][j] > getInput().getUb()[j]) p[i][j] = getInput().getUb()[j];
            }
            for (k = 0; k < n && k != i; k++) {
                calcI(i, k);
            }
            f[i] = getInput().getFunction().apply(p[i]);
            if (f[i] < y) {
                y = f[i];
                x = p[i].clone();
            }
        }
    }

    /**
     * 生成最优解
     */
    private void generateOptimization() {
        setOutput(new Output(y, x));
    }

    /**
     * 运行并计算结果
     */
    @Override
    public void go() {
        int t;
        for (t = 0; t < getIter(); t++) {
            scan(t);
        }
        generateOptimization();
    }

    /**
     * 运行并计算结果（按迭代逐步跟踪，自带运行，无需手动），如果目的是计算出结果，请使用{@link #go()}
     *
     * @param author   作者
     * @param message  备注
     * @param title    标题
     * @param fileType 文件类型 {@link FileType}
     * @return {@link Tracker}
     */
    @Override
    public Tracker tracker(String author, String message, String title, FileType fileType) {
        int t;
        Tracker tracker = new Tracker(this, author, message, title, fileType);
        tracker.startTracker();
        for (t = 0; t < getIter(); t++) {
            scan(t);
            tracker.trackOne(t + 1);
        }
        generateOptimization();
        tracker.endTracker();
        return tracker;
    }

    /**
     * 生成报告（要先手动运行才能生成报告）
     *
     * @param author   作者
     * @param message  备注
     * @param title    标题
     * @param fileType 文件类型 {@link FileType}
     * @return {@link Report}
     */
    @Override
    public Report report(String author, String message, String title, FileType fileType) {
        return new Report(this, author, message, title, fileType);
    }

    /**
     * 计算两个萤火虫之间的光强（1看向2的光强，2是对方）
     *
     * @param i 萤火虫 1 位置
     * @param k 萤火虫 2 位置
     */
    private void calcI(int i, int k) {
        I[i][k] = I_0[k] * Math.exp(-gamma * calcDistance(p[i], p[k]));
    }

    /**
     * 计算所有两个萤火虫之间的光强（行看向列的光强，列是对方）
     */
    private void calcAllI() {
        int n = getN();
        int i, j;
        for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++) {
                if (i == j) {
                    // 那就是自己了，用自身光强填充
                    I[i][j] = I_0[i];
                } else {
                    I[i][j] = I_0[j] * Math.exp(-gamma * calcDistance(p[i], p[j]));
                }
            }
        }
    }

    /**
     * 计算距离（欧几里得距离）
     *
     * @param p1 点1
     * @param p2 点2
     * @return double
     */
    private double calcDistance(double[] p1, double[] p2) {
        int j;
        double d2 = 0.0;
        for (j = 0; j < getInput().getVarNum(); j++) {
            d2 += (p1[j] - p2[j]) * (p1[j] - p2[j]);
        }
        return Math.sqrt(d2);
    }
}
